/*
 * Crypto wrapper for internal crypto implementation
 * Copyright (c) 2006-2009, Jouni Malinen <j@w1.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Alternatively, this software may be distributed under the terms of BSD
 * license.
 *
 * See README and COPYING for more details.
 */

#include "includes.h"

#include "common.h"
#include "crypto.h"
#include "sha1_i.h"
#include "md5_i.h"

struct crypto_hash {
    enum crypto_hash_alg alg;
    union {
        struct MD5Context md5;
        struct SHA1Context sha1;
    } u;
    u8 key[64];
    size_t key_len;
};


struct crypto_hash * crypto_hash_init(enum crypto_hash_alg alg, const u8 *key,
        size_t key_len)
{
    struct crypto_hash *ctx;
    u8 k_pad[64];
    u8 tk[20];
    size_t i;

    ctx = os_zalloc(sizeof(*ctx));
    if (ctx == NULL)
        return NULL;

    ctx->alg = alg;

    switch (alg) {
        case CRYPTO_HASH_ALG_MD5:
            MD5Init(&ctx->u.md5);
            break;
        case CRYPTO_HASH_ALG_SHA1:
            SHA1Init(&ctx->u.sha1);
            break;
        case CRYPTO_HASH_ALG_HMAC_MD5:
            if (key_len > sizeof(k_pad)) {
                MD5Init(&ctx->u.md5);
                MD5Update(&ctx->u.md5, key, key_len);
                MD5Final(tk, &ctx->u.md5);
                key = tk;
                key_len = 16;
            }
            os_memcpy(ctx->key, key, key_len);
            ctx->key_len = key_len;

            os_memcpy(k_pad, key, key_len);
            os_memset(k_pad + key_len, 0, sizeof(k_pad) - key_len);
            for (i = 0; i < sizeof(k_pad); i++)
                k_pad[i] ^= 0x36;
            MD5Init(&ctx->u.md5);
            MD5Update(&ctx->u.md5, k_pad, sizeof(k_pad));
            break;
        case CRYPTO_HASH_ALG_HMAC_SHA1:
            if (key_len > sizeof(k_pad)) {
                SHA1Init(&ctx->u.sha1);
                SHA1Update(&ctx->u.sha1, key, key_len);
                SHA1Final(tk, &ctx->u.sha1);
                key = tk;
                key_len = 20;
            }
            os_memcpy(ctx->key, key, key_len);
            ctx->key_len = key_len;

            os_memcpy(k_pad, key, key_len);
            os_memset(k_pad + key_len, 0, sizeof(k_pad) - key_len);
            for (i = 0; i < sizeof(k_pad); i++)
                k_pad[i] ^= 0x36;
            SHA1Init(&ctx->u.sha1);
            SHA1Update(&ctx->u.sha1, k_pad, sizeof(k_pad));
            break;
        default:
            os_free(ctx);
            return NULL;
    }

    return ctx;
}


void crypto_hash_update(struct crypto_hash *ctx, const u8 *data, size_t len)
{
    if (ctx == NULL)
        return;

    switch (ctx->alg) {
        case CRYPTO_HASH_ALG_MD5:
        case CRYPTO_HASH_ALG_HMAC_MD5:
            MD5Update(&ctx->u.md5, data, len);
            break;
        case CRYPTO_HASH_ALG_SHA1:
        case CRYPTO_HASH_ALG_HMAC_SHA1:
            SHA1Update(&ctx->u.sha1, data, len);
            break;
    }
}


int crypto_hash_finish(struct crypto_hash *ctx, u8 *mac, size_t *len)
{
    u8 k_pad[64];
    size_t i;

    if (ctx == NULL)
        return -2;

    if (mac == NULL || len == NULL) {
        os_free(ctx);
        return 0;
    }

    switch (ctx->alg) {
        case CRYPTO_HASH_ALG_MD5:
            if (*len < 16) {
                *len = 16;
                os_free(ctx);
                return -1;
            }
            *len = 16;
            MD5Final(mac, &ctx->u.md5);
            break;
        case CRYPTO_HASH_ALG_SHA1:
            if (*len < 20) {
                *len = 20;
                os_free(ctx);
                return -1;
            }
            *len = 20;
            SHA1Final(mac, &ctx->u.sha1);
            break;
        case CRYPTO_HASH_ALG_HMAC_MD5:
            if (*len < 16) {
                *len = 16;
                os_free(ctx);
                return -1;
            }
            *len = 16;

            MD5Final(mac, &ctx->u.md5);

            os_memcpy(k_pad, ctx->key, ctx->key_len);
            os_memset(k_pad + ctx->key_len, 0,
                    sizeof(k_pad) - ctx->key_len);
            for (i = 0; i < sizeof(k_pad); i++)
                k_pad[i] ^= 0x5c;
            MD5Init(&ctx->u.md5);
            MD5Update(&ctx->u.md5, k_pad, sizeof(k_pad));
            MD5Update(&ctx->u.md5, mac, 16);
            MD5Final(mac, &ctx->u.md5);
            break;
        case CRYPTO_HASH_ALG_HMAC_SHA1:
            if (*len < 20) {
                *len = 20;
                os_free(ctx);
                return -1;
            }
            *len = 20;

            SHA1Final(mac, &ctx->u.sha1);

            os_memcpy(k_pad, ctx->key, ctx->key_len);
            os_memset(k_pad + ctx->key_len, 0,
                    sizeof(k_pad) - ctx->key_len);
            for (i = 0; i < sizeof(k_pad); i++)
                k_pad[i] ^= 0x5c;
            SHA1Init(&ctx->u.sha1);
            SHA1Update(&ctx->u.sha1, k_pad, sizeof(k_pad));
            SHA1Update(&ctx->u.sha1, mac, 20);
            SHA1Final(mac, &ctx->u.sha1);
            break;
    }

    os_free(ctx);

    return 0;
}


int crypto_global_init(void)
{
    return 0;
}


void crypto_global_deinit(void)
{
}
